<?php

#namespace Giocondo;

# error -- Error logging functions.
class Error {
	# Set a custom error handler function. Aids in debugging inline event
	# code by adding the command, name of the event and the widget id.
	static public function set_handler() {
		# Limitations:
		# The following error types cannot be handled with a user defined function: 
		# E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, 
		# E_COMPILE_WARNING, and most of E_STRICT raised in the file where 
		# set_error_handler() is called.
		# (extracted from set_error_handler PHP manual page)
		#
		# This means that if one of these will occur in an
		# evalutated command (see eval function in Event.php)
		# there will be no additional logging information.
		#
		# Note we honor user configured PHP error_reporting value.
		$error_reporting = ini_get('error_reporting');
		set_error_handler('Error::handler', $error_reporting);

		# Tells the the PHP internal error handler to not print any errors
		# that can be (and actually are) handled by our custom handler thus
		# avoiding getting duplicate errors.  See also "Limitations" above.
		error_reporting($error_reporting & (E_ERROR | E_PARSE | E_CORE_ERROR |
			E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_STRICT));
	}

	static public function handler($errno, $errstr, $errfile, $errline) {
		global $Giocondo_callback, $Giocondo_command, $Giocondo_e_name,
			$Giocondo_id, $Giocondo_js;

		# Do print nothing: the statement that caused the error
		# was prepended by the @ error-control operator.
		if (ini_get('error_reporting') == 0) {
			return;
		}

		# Error level description. The same used by PHP.
		switch ($errno) {
			case E_NOTICE:
			case E_USER_NOTICE:
				$level = 'Notice';
			break;

			case E_WARNING:
			case E_USER_WARNING:
				$level = 'Warning';
			break;

			case E_USER_ERROR:
				$level = 'Fatal error';
			break;

			case E_STRICT:
				$level = 'Strict Standards';
			break;

			case E_RECOVERABLE_ERROR:
				$level = 'Catchable fatal error';
			break;

			case E_PARSE:
				# This case happens only if this handler is called directly.
				$level = 'Parse error';
			break;
			# There should not be other cases left over.
    }

		if (isset($Giocondo_callback)) {
			$errstr .= sprintf("%s '%s'", $errstr != '' ?
				' while executing' : 'Executing command', $Giocondo_command);
		}

		# Honor log_errors_max_len.
		if ($log_errors_max_len = ini_get('log_errors_max_len')) {
			# TODO: substr is binary safe? what about multibyte encoding
			$errstr = substr($errstr, 0, $log_errors_max_len);
		}

		$error = sprintf("%s:  %s%s in %s on line %s",
			$level, $errstr, $Giocondo_e_name ?
				sprintf(' in response to event %s of widget id %u',
					$Giocondo_e_name, $Giocondo_id)
			:
			'', $errfile, $errline);

		$display_errors = strtolower(ini_get('display_errors'));
		if ($display_errors == '1' || $display_errors ==  'on'
				|| $display_errors == 'stdout') {
			$html_errors = strtolower(ini_get('html_errors'));
			$html_errors = $html_errors == '1' || $html_errors == 'on';
			if ($html_errors) {
				# TODO: support for docref_root and docref_ext PHP options if not too 
				# difficult.
				$html_error = sprintf('<b>%s</b>:  %s%s in <b>%s</b> on line <b>%u</b>',
					$level, htmlspecialchars($errstr, ENT_NOQUOTES),
					$Giocondo_e_name ?
						sprintf(' in response to event <b>%s</b> of widget id <b>%u</b>',
							$Giocondo_e_name, $Giocondo_id)
					:
						'', htmlspecialchars($errfile, ENT_NOQUOTES), $errline);
			}

			# When handling an AJAX request errors messages can't be displayed to 
			# stdout because they will be evaluated as JavaScript code and almost 
			# certainly generate a client-side error. We choose to always use a 
			# custom JavaScript function to display them in a separate error console 
			# window opened by JavaScript.
			#
			# TODO: use the "don't call me I'll call you baby" mechanism for
			# Errors too because otherwise multiple errors windows can get opened?
			if (isset($Giocondo_id)) {
				$Giocondo_js[0]['onError'] .= sprintf("s('%s');", $html_errors ?
					String::filter($html_error, 'hj') : String::filter($error, 'j'));
			} else {
				printf("\n<script type=\"text/javascript\">with(Giocondo.getInstance()){s('%s');}</script>\n",
					$html_errors ?
						String::filter($html_error, 'hj')
					:
						String::filter($error, 'j'));
			}
		}

		if (ini_get('log_errors')) {
			error_log("PHP $error");
		}

		if ($errno == E_USER_ERROR) {
			exit();
		}

		# The error handler must return FALSE to allow the PHP internal error 
		# handler to execute and populate $php_errormsg (if track_errors feature is 
		# enabled).
		return FALSE;
	}

	# This method just provide an alias for global function trigger_error.
	static public function trigger($msg, $type = E_USER_NOTICE) {
		return trigger_error($msg, $type);
	}

	# Enable or disabled parse error report. Useful to wrap an eval call in order 
	# to provide a custom handling of a parse error occurring in the evaluated 
	# code.
	static public function parse($report) {
		$error_reporting = ini_get('error_reporting');
		error_reporting($report ? $error_reporting | E_PARSE
			: $error_reporting & ~E_PARSE);
	}

	static public function set_exception_handler() {
		set_exception_handler(array(get_class(), 'exception_handler'));
	}

	static public function exception_handler($e) {
		self::handler(E_NOTICE, $e->getMessage(), $e->getFile(), $e->getLine());
	}
}

# End of file
# vim: set ts=2 sw=2 noet:
?>
